// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Author: Markus Gutschke

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "check.h"
#include "utf8.h"
#include "xml.h"


static void trim(const char **s, size_t *len) {
  // Trim white space
  for (; **s > '\000' && **s <= ' '; ++*s, --*len) { }
  for (; *len && (*s)[*len - 1] <= ' '; --*len) { }
}

static size_t rep_entities(char *buf) {
  // Replace XML entities with UTF-8 encoded characters.
  size_t len = strlen(buf);
  for (char *ptr = buf; (ptr = strchr(ptr, '&')) != NULL; ) {
    // Scan for well-known symbolic entities such as "&amp;"
    static const struct Rep {
      const char     *entity;
      const unsigned cp;
    } rep[] = { { "lt", '<' }, { "gt", '>' }, { "amp", '&' },
                { "apos", '\'' }, { "quot", '"' } };
    size_t l = strcspn(ptr + 1, ";");
    check(ptr[l + 1] == ';');
    unsigned cp;
    for (int i = 0; i < sizeof(rep)/sizeof(*rep); ++i) {
      if (l == strlen(rep[i].entity) && !memcmp(ptr + 1, rep[i].entity, l)) {
        cp = rep[i].cp;
        goto replace;
      }
    }

    // Also allow numeric entities
    check(ptr[1] == '#');
    unsigned long val;
    char *endptr;
    if ((ptr[2] | 0x20) == 'x') {
      // Hexadecimal
      check((ptr[3] >= '0' && ptr[3] <= '9') ||
            ((ptr[3] | 0x20) >= 'a' && (ptr[3] | 0x20) <= 'f'));
      val = strtoul(ptr + 3, &endptr, 16);
    } else {
      // Decimal
      check(ptr[2] >= '0' && ptr[2] <= '9');
      val = strtoul(ptr + 2, &endptr, 10);
    }
    check(endptr == ptr + l + 1);
    check(val);
    check(val != ULONG_MAX);
    check(val < 0x110000u);
    cp = (unsigned)val;

    // Replace entity with UTF-8 encoded character. UTF-8 is guaranteed to
    // be more compact than any of the entity encodings. So, we only need
    // to call memmove() once we are done.
  replace:
    endptr = encodeUTF8(ptr, cp);
    check(endptr <= ptr + l + 2);
    memmove(endptr, ptr + l + 2, len - (ptr + l + 2 - buf) + 1);
    len -= ptr + l + 2 - endptr;
    ++ptr;
  }
  return len;
}

static const char *internString(struct XMLState *state,
                                struct HashMap *hashmap, const char *s,
                                size_t len, int trim_space,
                                int parse_entities, int casefold) {
  // Interning strings makes memory management much easier. It also allows
  // us to compare string addresses instead of string contents, if we so
  // choose to do.

  // If requested, trim leading and trailing white-space
  if (trim_space) {
    trim(&s, &len);
  }

  // Copy string into a singleton helper and terminate with NUL
  if (len >= state->tmpSz) {
    check(state->tmpBuf = realloc(state->tmpBuf, state->tmpSz = len + 1));
  }
  memcpy(state->tmpBuf, s, len);
  state->tmpBuf[len] = '\000';

  // Parse entities and replace them with UTF-8 characters
  if (parse_entities) {
    rep_entities(state->tmpBuf);
  }

  // Convert to lower case, if requested
  if (casefold) {
    for (char *ptr = state->tmpBuf; *ptr; ++ptr) {
      if (*ptr >= 'A' && *ptr <= 'Z') {
        *ptr += 0x20;
      }
    }
  }

  // If the string can already be found in the hash map, return the interned
  // version. Otherwise, add a new copy to the hash map.
  const char *ret = getFromHashMap(hashmap, state->tmpBuf);
  if (!ret) {
    check(ret = strdup(state->tmpBuf));
    addToHashMap(hashmap, ret, ret);
  }
  return ret;
}

static void deleteInternedString(void *arg, char *key, char *value) {
  // Interned strings are kept in a HashMap, which is treated as if it was a
  // hash set. We only need to free either the key or the value, never both.
  free(key);
}

static void deleteAttributes(void *arg, char *key, char *value) {
  // All attributes and their values are interned and don't need deletion.
}

static void deleteNSEntry(void *arg, char *key, char *value) {
  // All namespace names and values are interned and don't need deletion.
}

static void initXMLElement(struct XMLElement *elem, struct XML *xml,
                           struct XMLElement *parent, enum XMLType type,
                           const char *s, size_t len);
static struct XMLElement *newXMLElement(struct XML *xml,
                                        struct XMLElement *parent,
                                        enum XMLType type, const char *s,
                                        size_t len) {
  struct XMLElement *elem;
  check(elem = malloc(sizeof(struct XMLElement)));
  initXMLElement(elem, xml, parent, type, s, len);
  return elem;
}

static void initXMLElement(struct XMLElement *elem, struct XML *xml,
                           struct XMLElement *parent, enum XMLType type,
                           const char *s, size_t len) {
  memset(elem, 0, sizeof(struct XMLElement));
  elem->parent = parent;
  elem->type = type;
  initHashMap(&elem->attributes, deleteAttributes, NULL);
  initHashMap(&elem->ns, deleteNSEntry, NULL);
  switch (type) {
    // And XMLElement can store either an actual element or some character data
  case X_ELEMENT:
    elem->element = (char *)internString(&xml->state, &xml->elements,
                                         s, len, 1, 0, 1);
    break;
  case X_CDATA:
    trim(&s, &len);
    check(elem->cdata = malloc(len + 1));
    memcpy(elem->cdata, s, len);
    elem->cdata[len] = '\000';
    rep_entities(elem->cdata);
    break;
  default:
    check(0);
    break;
  }

  // Add this element to its parent element (if any).
  if (parent) {
    check(parent->children = realloc(parent->children,
                       sizeof(struct XMLElement *) * ++parent->children_size));
    parent->children[parent->children_size-1] = elem;
  }
}

static void deleteXMLElement(struct XMLElement *elem);
static void destroyXMLElement(struct XMLElement *elem) {
  if (elem) {
    if (elem->type == X_CDATA) {
      free(elem->cdata);
    }
    destroyHashMap(&elem->attributes);
    destroyHashMap(&elem->ns);
    for (size_t i = 0; i < elem->children_size; ++i) {
      deleteXMLElement(elem->children[i]);
    }
    free(elem->children);
  }
}

static void deleteXMLElement(struct XMLElement *elem) {
  destroyXMLElement(elem);
  free(elem);
}

struct XML *newXML() {
  struct XML *xml;
  check(xml = malloc(sizeof(struct XML)));
  initXML(xml);
  return xml;
}

void initXML(struct XML *xml) {
  memset(xml, 0, sizeof(*xml));
  initHashMap(&xml->namespaces, deleteInternedString, NULL);
  initHashMap(&xml->elements,   deleteInternedString, NULL);
  initHashMap(&xml->attributes, deleteInternedString, NULL);
  initHashMap(&xml->misc,       deleteInternedString, NULL);
}

void destroyXML(struct XML *xml) {
  if (xml) {
    free(xml->state.buf);
    free(xml->state.tmpBuf);
    deleteXMLElement(xml->state.element);
    deleteHashMap(xml->ns);
    destroyHashMap(&xml->namespaces);
    destroyHashMap(&xml->elements);
    destroyHashMap(&xml->attributes);
    destroyHashMap(&xml->misc);
  }
}

void deleteXML(struct XML *xml) {
  destroyXML(xml);
  free(xml);
}

void resetXML(struct XML *xml) {
  // Resets state of XML element, but retains buffer contents of any
  // data that might already have been read, but not yet parsed.
  deleteXMLElement(xml->state.element);
  deleteHashMap(xml->ns);
  destroyHashMap(&xml->namespaces);
  destroyHashMap(&xml->elements);
  destroyHashMap(&xml->attributes);
  destroyHashMap(&xml->misc);
  xml->state.doc = 0;
  xml->state.element = NULL;
  xml->ns = NULL;
  initHashMap(&xml->namespaces, deleteInternedString, NULL);
  initHashMap(&xml->elements,   deleteInternedString, NULL);
  initHashMap(&xml->attributes, deleteInternedString, NULL);
  initHashMap(&xml->misc,       deleteInternedString, NULL);
}

struct NSIteratorArgs {
  struct XML        *xml;
  struct XMLElement *elem;
  struct HashMap    *ns;
};

static int attributeIterator(void *arg_, const char *key, char **value) {
  // Iterate over all the attributes of an element and find namespace
  // definitions (i.e. anything that says either "xmlns" or that starts
  // with "xmlns:...")
  struct NSIteratorArgs *arg = (struct NSIteratorArgs *)arg_;
  if (!strncmp(key, "xmlns", 5) && (!key[5] || key[5] == ':')) {
    const char *ns_name = key + 5;
    if (*ns_name == ':') {
      ++ns_name;
    }
    ns_name = internString(&arg->xml->state, &arg->xml->namespaces, ns_name,
                           strlen(ns_name), 0, 0, 0);
    const char *ns_value = internString(&arg->xml->state,
                                        &arg->xml->namespaces, *value,
                                        strlen(*value), 0, 0, 0);
    addToHashMap(arg->ns, ns_name, ns_value);
  }
  return 1;
}

static void normalizeNamespaces(struct XML *xml, struct XMLElement *elem) {
  if (!xml->state.doc) {
    elem->namespace = "";
  } else {
    // Only operate on the actual XML document. We don't need to handle
    // namespaces in the "<?xml ... ?>" document declaration nor in any
    // entity definitions.
    // We also assume that in a well-formed XMPP session, there is no
    // top-level character data, nor any character data in between XML
    // stanzas.
    check(elem->type == X_ELEMENT);

    // For the top-level "<stream>" element, we store namespace information
    // in the XML object. This way, we can access it from all XML stanzas.
    int global = !xml->ns;
    if (global) {
      xml->ns = newHashMap(deleteNSEntry, NULL);
      addToHashMap(xml->ns, "xml", "http://www.w3.org/XML/1998/namespace");
    }

    // Iterate over all element attributes and apply namespace information.
    struct NSIteratorArgs args = {
      .xml  = xml,
      .elem = elem,
      .ns   = global ? xml->ns : &elem->ns,
    };
    iterateOverHashMap(&elem->attributes, attributeIterator, &args);

    // Recursively iterate over all child elements and look for any namespace
    // information. This has to happen after the parent's namespace information
    // has been retrieved, as the child inherits the parent's namespace data.
    for (int i = 0; i < elem->children_size; ++i) {
      if (elem->children[i]->type == X_ELEMENT) {
        normalizeNamespaces(xml, elem->children[i]);
      }
    }

    // Apply the most specific matching namespace information to our element.
    check(!elem->namespace);
    const char *delim = strchr(elem->element, ':');
    char *namespace;
    if (delim) {
      // The element has an explicit name space selector.
      check(namespace = malloc(delim - elem->element + 1));
      memcpy(namespace, elem->element, delim - elem->element);
      namespace[delim - elem->element] = '\000';
    } else {
      // The element is in the current default namespace.
      namespace = NULL;
    }

    // Search through our namespace records, and if nothing matches keep
    // looking through our ancestor's namespace records.
    for (struct XMLElement *e = elem; e; e = e->parent) {
      if ((elem->namespace = (char *)getFromHashMap(&e->ns,
                                        namespace ? namespace : "")) != NULL) {
        break;
      }
    }

    // If we still haven't been able to resolve the namespace, look in the
    // global namespace records. These are the ones from the top-level
    // "<stream>" element.
    if (!elem->namespace) {
      check(elem->namespace = (char *)getFromHashMap(xml->ns,
                                                  namespace ? namespace : ""));
    }
    free(namespace);

    // If the element explicitly selected a particular namespace, drop this
    // selector. We want just the plain element name. Since we use interned
    // strings, it is OK to adjust the start address without doing any copying.
    if (delim) {
      elem->element = (char *)delim + 1;
      check(*elem->element);
    }
  }
}

size_t parseXML(struct XML *xml, const char *txt, size_t len,
               int (*fnc)(struct XML *xml, struct XMLElement *elem, void *arg),
               void *arg) {
  // Please note, this is not a generic XML parser. It is specifically designed
  // for the needs of a Jabber client.
  // In particular, it doesn't have any of the features that are unneeded by
  // Jabber (e.g. schema parsing, dynamic definition of new entities, transport
  // encodings other than UTF-8, ...)
  // On the other hand, it knows how to break the XML stream apart into
  // stanzas, which makes it much easier to build a Jabber client on top of
  // this parser.
  // A Jabber session looks something like this:
  //
  //  <?xml version="1.0"?>
  //  <stream:stream to="example.com" xmlns="jabber:client"
  //                 xmlns:stream="http://etherx.jabber.org/streams"
  //                 version="1.0">
  //    <stream:features>
  //      <starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls">
  //        <required/>
  //      </starttls>
  //      <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
  //        <mechanism>DIGEST-MD5</mechanism>
  //        <mechanism>PLAIN</mechanism>
  //      </mechanisms>
  //    </stream:features>
  //  </stream>

  // We use a state machine to parse elements. Some of the state is only
  // needed while inside of an element definition. This state is kept in
  // local variables, and by looking for ">" characters we can ensure that
  // we only ever parse complete elements.
  // State that needs to persist from one element tag to the next, is kept
  // in an XMLState object that is stored as part of the global XML object.
  enum State {
    X_TOP, X_CDATA, X_LANGLE, X_ENDTAG, X_EXCL, X_COMMENT0, X_COMMENT1,
    X_COMMENT2, X_COMMENT3, X_COMMENT4, X_COMMENT5, X_COMMENT6, X_SKIP,
    X_ELEM, X_INSIDE_ELEM, X_ATTR, X_ATTR_VALUE, X_QUOTED_STRING,
    X_ELEM_CLOSED, X_STREAM_CLOSED,
  } state = X_TOP, prev = X_TOP;

  if (xml->state.sz - xml->state.len <= len) {
    // Copying all data to a reassembly buffer is slightly more expensive, but
    // significantly simplifies the state machine. We never expect to parse
    // huge XML documents, so the performance hit should be negligible.
    check(xml->state.buf = realloc(
                 xml->state.buf,
                 xml->state.sz = ((xml->state.len + len + 1 + 1023) & ~1023)));
  }
  memcpy(xml->state.buf + xml->state.len, txt, len);
  xml->state.len += len;
  xml->state.buf[xml->state.len] = '\000';

  char *last = memrchr(xml->state.buf, '>', xml->state.len);
  if (!last) {
    // Incomplete element in buffer. Return to caller. Trying to partially
    // parse the element just complicates the state machine (especially, if
    // encountering a partial UTF-8 character), but doesn't actually result
    // in a callback.
    return 0;
  }
  const char *ptr;
  const char *sym = NULL;
  const char *attr = NULL;
  int decl = 0;
  unsigned quote = 0;
  for (ptr = xml->state.buf; ptr <= last; ) {
    unsigned cp;
    const char *old_ptr = ptr;
    ptr = parseUTF8(ptr, &cp);
    check(ptr);

    switch (state) {
    case X_TOP:
      // We are outside of any elements. Skip white space until we find the
      // opening left angle bracket of a new element or some character data.
      if (cp <= ' ') {
        continue;
      }
      if (cp != '<') {
        state = X_CDATA;
        prev = X_CDATA;
        sym = old_ptr;
      } else {
        prev = X_TOP;
        state = X_LANGLE;
      }
      break;
    case X_CDATA:
      // Read character data until we find the opening left angle bracket of
      // a new element.
      if (cp == '<') {
        // Add character data (if any) as child to the current element.
        state = X_LANGLE;
        if (old_ptr != sym) {
          newXMLElement(xml, xml->state.element, X_CDATA, sym, old_ptr - sym);
        }
      }
      break;
    case X_LANGLE:
      // We are looking at the first character after an opening left
      // bracket.
      check(cp > ' ');
      check(cp != '<');
      check(cp != '>');
      if (cp == '!') {
        // This could either be a comment starting with "<!--" or it could
        // be an entity definition starting with "<!"
        state = X_EXCL;
      } else {
        // Allow comments in elements, but not elements in elements
        check(prev == X_TOP || prev == X_CDATA);
        decl = 0;
        if (!xml->state.doc) {
          if (cp == '?') {
            // If the element starts with "<?" this is a document declaration.
            // Most likely something like "<?xml version='1.0'?>"
            decl = 1;
          }
        }
        if (cp == '/') {
          // If the element starts with "</", this is actually an end tag.
          state = X_ENDTAG;
          sym = ptr;
          continue;
        } else {
          // Looks as if we encountered a vanilla element.
          state = X_ELEM;
        }
      }
      // Remember the name of the element.
      sym = old_ptr;
      break;
    case X_ENDTAG:
      // Read remaining characters in end tag.
      if (cp == '>') {
        goto elem_closed;
      }
      break;
    case X_EXCL:
      // We have so far read "<!". We now need to check whether this is a
      // comment or a entity definition.
      if (cp == '-') {
        state = X_COMMENT3;
      } else {
        // Allow comments in elements, but not elements in elements
      entity:
        check(prev == X_TOP || prev == X_CDATA);
        check(!xml->state.doc);
        state = X_SKIP;
      }
      break;
    case X_COMMENT0:
      // We read a "<" inside of an element. This is only allowed if it is the
      // start of a comment.
      check(cp == '!');
      state = X_COMMENT1;
      break;
    case X_COMMENT1:
      // We read "<!" inside of an element. This is only allowed if it is the
      // start of a comment.
      check(cp == '-');
      state = X_COMMENT2;
      break;
    case X_COMMENT2:
      // We read "<!-" inside of an element. This is only allowed if it is the
      // start of a comment.
      check(cp == '-');
      state = X_COMMENT4;
      break;
    case X_COMMENT3:
      // We have so far read "<!-". Check if we get another "-" completing the
      // opening of a comment.
      if (cp == '-') {
        state = X_COMMENT4;
      } else {
        goto entity;
      }
    break;
    case X_COMMENT4:
      // We are inside of a comment looking for the closing "-->" characters.
      if (cp == '-') {
        state = X_COMMENT5;
      }
      break;
    case X_COMMENT5:
      // We are inside of a comment and have read "-", check if this is the
      // closing part of the comment.
      if (cp == '-') {
        state = X_COMMENT6;
      } else {
        state = X_COMMENT4;
      }
      break;
    case X_COMMENT6:
      // We are inside of a comment and have read "--", check if this is the
      // closing part of the comment.
      if (cp == '>') {
        state = prev;
        sym = ptr;
      } else {
        state = X_COMMENT4;
      }
      break;
    case X_SKIP:
      // We skip entity tags but allow comments in them.
      if (cp == '<') {
        prev = X_SKIP;
        state = X_COMMENT0;
      } else if (cp == '>') {
        state = X_TOP;
      }
      break;
    case X_ELEM:
      // We are reading the name of an element.
      if (cp <= ' ') {
        state = X_INSIDE_ELEM;
      } else if (cp == '>') {
        state = X_TOP;
      } else if (cp == '<') {
        prev = X_INSIDE_ELEM;
        state = X_COMMENT0;
      } else if (cp == '/') {
        state = X_ELEM_CLOSED;
      } else {
        continue;
      }
      check(old_ptr != sym);
      xml->state.element = newXMLElement(xml, xml->state.element, X_ELEMENT,
                                         sym, old_ptr - sym);
      if (cp == '>' && !xml->state.doc) {
        goto elem_closed;
      }
      break;
    case X_INSIDE_ELEM:
      // We are inside of an element tag and in between attributes.
      if (cp <= ' ') {
        continue;
      } else if (cp == '>') {
        if (cp == '>' && !xml->state.doc) {
          goto elem_closed;
        }
        state = X_TOP;
      } else if (cp == '/') {
        state = X_ELEM_CLOSED;
      } else if (cp == '<') {
        prev = X_INSIDE_ELEM;
        state = X_COMMENT0;
      } else {
        state = X_ATTR;
        sym = old_ptr;
      }
      break;
    case X_ATTR:
      // We are reading the name of an attribute.
      if (cp <= ' ') {
        state = X_INSIDE_ELEM;
      } else if (cp == '>') {
        if (cp == '>' && !xml->state.doc) {
          goto elem_closed;
        }
        state = X_TOP;
      } else if (cp == '<') {
        prev = X_INSIDE_ELEM;
        state = X_COMMENT0;
      } else if (cp == '/') {
        state = X_ELEM_CLOSED;
      } else if (cp == '=') {
        state = X_ATTR_VALUE;
      } else {
        continue;
      }
      check(old_ptr != sym);
      if (old_ptr - sym == 1 && *sym == '?') {
        check(decl);
        goto elem_closed;
      } else {
        attr = internString(&xml->state, &xml->attributes, sym,
                            old_ptr - sym, 1, 0, 1);
        if (cp != '=') {
          addToHashMap(&xml->state.element->attributes, attr, "");
        }
      }
      break;
    case X_ATTR_VALUE:
      // We are reading the value of an attribute. It must be quoted.
      check(cp == '"' || cp == '\'');
      quote = cp;
      sym = ptr;
      state = X_QUOTED_STRING;
      break;
    case X_QUOTED_STRING:
      // We are reading the quoted string that makes up the value of
      // an attribute.
      if (cp == quote) {
        state = X_INSIDE_ELEM;
        const char *value = internString(&xml->state, &xml->misc, sym,
                                         old_ptr - sym, 0, 1, 0);
        addToHashMap(&xml->state.element->attributes, attr, value);
      }
      break;
    case X_ELEM_CLOSED:
    elem_closed:
      check(cp == '>');

      // The state machine is breaking up the stream of elements into
      // XML stanzas. But we can only start doing so, once we are inside the
      // top-level <stream> tag. This also means, when we see the closing
      // </stream> tag, we won't have an active element.
      xml->state.doc |= !decl;
      if (!xml->state.element) {
        state = X_STREAM_CLOSED;
        fnc(xml, NULL, arg);
        goto done;
      }
      state = X_TOP;
      check(xml->state.element);
      if (!xml->state.element->parent) {
        // If we don't have any parent element (other than the top-level
        // <stream> tag, the stanza is complete. Notify the callback.
        normalizeNamespaces(xml, xml->state.element);
        int rc = fnc(xml, xml->state.element, arg);
        deleteXMLElement(xml->state.element);
        xml->state.element = NULL;
        if (!rc) {
          // If the callback function returned "zero", stop parsing here.
          // This is necessary when switching XMPP streams to a new mode
          // (e.g. after TLS and after SASL negotiation).
          goto done;
        }
      } else {
        // We encountered the closing tag for a child element. Now continue
        // with the parent element.
        xml->state.element = xml->state.element->parent;
      }
      break;
    case X_STREAM_CLOSED:
      check(0);
    default:
      check(0);
    }
  }
 done:
  check(state == X_TOP);
  check(xml->state.sz > 0);
  memmove(xml->state.buf, ptr, xml->state.len + 1 - (ptr - xml->state.buf));
  xml->state.len -= ptr - xml->state.buf;
  return ptr - xml->state.buf;
}

char *xmlEscape(const char *s) {
  char *res = NULL;
  size_t sz = 0;
  size_t len = 0;
  for (;;) {
    if (sz < len + 10) {
      check(res = realloc(res, sz += 40));
    }
    unsigned codepoint;
    s = parseUTF8(s, &codepoint);
    if (!s) {
      res[len++] = '\000';
      check(res = realloc(res, len));
      return res;
    } else if (codepoint == '&') {
      strcpy(res + len, "&amp;");
      len += 5;
    } else if (codepoint == '<') {
      strcpy(res + len, "&lt;");
      len += 4;
    } else if (codepoint == '>') {
      strcpy(res + len, "&gt;");
      len += 4;
    } else if (codepoint == '\'') {
      strcpy(res + len, "&apos;");
      len += 6;
    } else if (codepoint == '"') {
      strcpy(res + len, "&quot;");
      len += 6;
    } else if (codepoint < ' ' || codepoint >= 0x80) {
      sprintf(res + len, "&#x%x;", codepoint);
      len += strlen(res + len);
    } else {
      res[len++] = (char)codepoint;
    }
  }
}

int xmlElementMatches(struct XMLElement *elem, const char *namespace,
                      const char *element) {
  return elem && elem->type == X_ELEMENT &&
         (!namespace || !strcmp(elem->namespace, namespace)) &&
         (!element || !strcmp(elem->element, element));
}

int xmlElementHasChild(struct XMLElement *elem, struct XMLElement **child,
                       const char *namespace, const char *element,
                       const char *childs_cdata) {
  if (!elem || elem->type != X_ELEMENT) {
    return 0;
  }
  for (size_t i = 0; i < elem->children_size; ++i) {
    struct XMLElement *c = elem->children[i];
    if (c->type != X_ELEMENT) {
      continue;
    }
    if (namespace && strcmp(c->namespace, namespace)) {
      continue;
    }
    if (element && strcmp(c->element, element)) {
      continue;
    }
    if (childs_cdata) {
      if (c->children_size != 1 || c->children[0]->type != X_CDATA ||
          strcmp(c->children[0]->cdata, childs_cdata)) {
        continue;
      }
    }
    if (child) {
      *child = c;
    }
    return 1;
  }
  return 0;
}

int xmlAttributeMatches(struct XMLElement *elem, const char *attr,
                        const char *value) {
  const char *v;
  return elem && elem->type == X_ELEMENT &&
         (v = getFromHashMap(&elem->attributes, attr)) != NULL &&
         !strcmp(v, value);
}
